/* 
 * Copyright (c) 2002 David Corbin and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Common Public License v0.5
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v05.html
 * 
 * Contributors:
 * 	  David Corbin  - initial implementation - currently delegates to PerforceUtil
 * 					- add moveFile method and several supporting routines
 *    Boris Pruessmann - Changes to determineFolderPosition, improved moveFile,
 *                       cleanup
 */
package net.sourceforge.perforce.core.internal.api;

import java.io.File;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import net.sourceforge.perforce.api.IPerforceCommand;
import net.sourceforge.perforce.core.PerforceCorePlugin;
import net.sourceforge.perforce.core.PerforceException;
import net.sourceforge.perforce.core.api.IChangeResult;
import net.sourceforge.perforce.core.api.IChangesResult;
import net.sourceforge.perforce.core.api.IClientForm;
import net.sourceforge.perforce.core.api.ICountersResult;
import net.sourceforge.perforce.core.api.IDescribeResult;
import net.sourceforge.perforce.core.api.IInfoResult;
import net.sourceforge.perforce.core.api.ILogEntry;
import net.sourceforge.perforce.core.api.IOpenedResult;
import net.sourceforge.perforce.core.api.IPerforceServer;
import net.sourceforge.perforce.core.api.IResolveResult;
import net.sourceforge.perforce.core.api.ISubmitResult;
import net.sourceforge.perforce.core.api.IWhereResult;
import net.sourceforge.perforce.core.internal.P4TeamProvider;
import net.sourceforge.perforce.core.internal.Policy;
import net.sourceforge.perforce.core.resources.IChangelist;
import net.sourceforge.perforce.core.resources.LocalFile;
import net.sourceforge.perforce.core.resources.LocalResource;
import net.sourceforge.perforce.core.resources.RemoteFile;
import net.sourceforge.perforce.core.resources.RemoteFolder;
import net.sourceforge.perforce.core.resources.RemoteResource;
import net.sourceforge.perforce.core.syncinfo.FolderSyncInfo;
import net.sourceforge.perforce.core.syncinfo.ResourceSyncInfo;
import org.eclipse.core.internal.localstore.CoreFileSystemLibrary;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.team.IResourceTree;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.PlatformObject;
import org.eclipse.core.runtime.Status;
import org.eclipse.team.core.RepositoryProvider;
import org.eclipse.team.core.TeamException;

/**
  * @version 	$Revision: 83 $
  * @author		<a href="mailto:dcorbin@users.sourceforge.net">David Corbin</a>
  * @author 	<a href="mailto:bpruessmann@users.sourceforge.net">Boris Pruessmann</a>
  */
public class PerforceServer extends PlatformObject implements IPerforceServer
{
	// --------------------------------------------------------------------------- Private Constants
		
	/** The name of the password property. */
	public static final String INFO_PASSWORD = 
		"net.sourceforge.perforce.core.password";                                      //$NON-NLS-1$
	/** The auth scheme. */
	public static final String AUTH_SCHEME = "";                                       //$NON-NLS-1$
	/** A faked url. */
	public static final URL FAKE_URL;

	static 
	{
		URL temp = null;
		try 
		{
			temp = new URL("http://net.sourceforge.perforce.core");                    //$NON-NLS-1$
		} 
		catch (MalformedURLException e) 
		{
			// Ignored! May not happen.
		}
		FAKE_URL = temp;
	} 
	
	// --------------------------------------------------------------------------- Private Variables

	/** Stores a reference to the last result of p4 info. */
	private IInfoResult perforceInfoResult;
	/** Stores the P4PORT value. */
	private String port;
	/** Stores the P4CLIENT value. */	
	private String client;
	/** Stores the P4USER value. */
	private String user;
	/** Stores the P4PASSWD value. */
	private String password;
	/** Stores the server address. */
	private String serverAddress;
	/** Stores the server root. */
	private String serverRoot;	
	/** Stores the server date. */
	private String serverDate;	
	/** Stores the server version. */
	private String serverVersion;	
	/** Stores the server license. */
	private String serverLicense;	
	/** Stores the client root */	
	private IPath clientRoot;
	/** Stores the connection to the server. */
	private Connection connection = new Connection(this);
	
	// -------------------------------------------------------------------------------- Constructors

	/** Constructor for PerforceServer.
	  * <br><b>Only PerforceServerManager may create those ...</b>
	  * 
	  * @param port value of P4PORT.
	  * @param client value of P4CLIENT.
	  * @param user value of P4USER.
	  * @param password value of P4PASSWD.
	  */
	PerforceServer(String port, String client, String user, String password)
	{
		this.port = port;
		this.client = client;
		this.user = user;
		this.password = password;
	}

	// ------------------------------------------------------------ Public Methods (IPerforceServer)

	/** @see IPerforceServer#members(IProgressMonitor) */
	public RemoteResource[] members(IProgressMonitor monitor)
		throws PerforceException
	{
		monitor = Policy.monitorFor(monitor);
					
		String[] depots = depots(monitor);
	
		List result = new ArrayList();
		for (int i = 0; i < depots.length; i++)
		{
			FolderSyncInfo[] syncInfos = dirs(depots[i], monitor);
			if (syncInfos.length == 1)
			{
				result.add(new RemoteFolder(this, null, syncInfos[0], "#head"));
			}
		}
		
		return (RemoteResource[]) result.toArray(new RemoteResource[result.size()]);
	}

	/** @see net.sourceforge.perforce.core.api.IPerforceServer#getIResource(String) */
	public IFile getFile(String filename) 
		throws PerforceException 
	{
		IWorkspaceRoot root = PerforceCorePlugin.getWorkspace().getRoot();
		
		IWhereResult whereResult = where(filename);
		return root.getFile(new Path(whereResult.getLocalFilename()));
	}


	/** @see IPerforceServer#getUniqueName()*/
	public String getUniqueName() 
	{
		return "p4port=" + getPort() + ",p4client=" + getClient() + ",p4user=" + getUser();
	}

	/** @see IPerforceServer#getPort() */
	public String getPort() 
	{
		return port;
	}

	/** @see IPerforceServer#getClientName() */
	public String getClient() 
	{
		return client;
	}
	
	/** @see IPerforceServer#getUser() */
	public String getUser() 
	{
		return user;
	}
	
	/** @see IPerforceServer#getPassword() */
	public String getPassword() 
	{
		if (password != null)
		{
			return password;
		}
		
		Map map = Platform.getAuthorizationInfo(FAKE_URL, getUniqueName(), AUTH_SCHEME);
		if (map != null)
		{
			String password = (String) map.get(INFO_PASSWORD);
			if (password != null)
			{
				return password;
			}
		}
		
		return "";
	}


	/** @see IPerforceServer#change(String) */
	public void change(ChangeSpec changeSpec)
		throws PerforceException
	{
		Change clientUser = Change.newInstanceForOutput(changeSpec.toString());
		run(new String[]{ "-i" }, clientUser, null);                                   //$NON-NLS-1$

		if (clientUser.getStatus().getSeverity() == IStatus.ERROR)
		{
			throw new PerforceException(clientUser.getStatus());
		}
	}

	/** @see IPerforceServer#change(Integer) */
	public IChangeResult change(Integer changelist)
		throws PerforceException
	{
		String[] arguments = changelist != null && changelist.intValue() != -1 ?
		                     new String[]{ "-o", changelist.toString() } :         //$NON-NLS-1$
		                     new String[]{ "-o" };                                 //$NON-NLS-2$
		                     
		Change clientUser = Change.newInstanceForInput();
       
		run(arguments, clientUser, null); 

		if (clientUser.getStatus().getSeverity() == IStatus.ERROR)
		{
			throw new PerforceException(clientUser.getStatus());
		}
			
		return clientUser.getResult();
	}

	/** @see IPerforceServer#changes(String) */
	public IChangesResult[] changes(String status, int count) 
		throws PerforceException
	{
		Policy.assertTrue("pending".equals(status) ||                                  //$NON-NLS-1$
		                  "submitted".equals(status));                                 //$NON-NLS-1$
	
		String[] arguments = 
			(count == Integer.MAX_VALUE) ?
		    new String[] { "-l", "-s", status } :                    //$NON-NLS-1$ //$NON-NLS-2$
		    new String[] { "-l", "-m",                               //$NON-NLS-1$ //$NON-NLS-2$
				Integer.toString(count), "-s", status };                           //$NON-NLS-1$
		
		Changes clientUser = Changes.newInstance(this);
		run(arguments, clientUser, null); 
		
		if (clientUser.getStatus().getSeverity() == IStatus.ERROR)
		{
			throw new PerforceException(clientUser.getStatus());
		}
			
		return clientUser.getResult();
	}
	
	/** @see IPerforceServer#counters() */
	public ICountersResult counters() 
		throws PerforceException
	{
		Counters clientUser = Counters.newInstance();
		run(new String[0], clientUser, null);

		if (clientUser.getStatus().getSeverity() == IStatus.ERROR)
		{
			throw new PerforceException(clientUser.getStatus());
		}
			
		return clientUser.getResult();
	}

	/** @see IPerforceServer#depots() */
	public String[] depots(IProgressMonitor monitor)
		throws PerforceException
	{
		Depots clientUser = Depots.newInstance(monitor);
		run(new String[0], clientUser, monitor);
		
		if (clientUser.getStatus().getSeverity() == IStatus.ERROR)
		{
			throw new PerforceException(clientUser.getStatus());
		}
					
		return clientUser.getResult();			
	}

	/** @see IPerforceServer#describe(Integer) */
	public IDescribeResult describe(Integer changelist)
		throws PerforceException
	{
		Describe clientUser = Describe.newInstance();
		run(new String[] { changelist.toString() }, clientUser, null);
		
		if (clientUser.getStatus().getSeverity() == IStatus.ERROR)
		{
			throw new PerforceException(clientUser.getStatus());
		}
			
		return clientUser.getResult();
	}

	/** @see IPerforceServer#dirs(String) */
	public FolderSyncInfo[] dirs(String dir, IProgressMonitor monitor)
		throws PerforceException
	{
		Dirs clientUser = Dirs.newInstance(monitor);
		run(new String[] { "-C", dir }, clientUser, monitor);                          //$NON-NLS-1$

		if (clientUser.getStatus().getSeverity() == IStatus.ERROR)
		{
			throw new PerforceException(clientUser.getStatus());
		}
			
		return clientUser.getResult();
	}

	/** @see IPerforceServer#opened(Integer) */
	public IOpenedResult[] opened(IChangelist changelist) 
		throws PerforceException
	{
		String changelistString = changelist.getId() == IChangelist.DEFAULT ? 
		                          "default" : changelist.getId().toString();           //$NON-NLS-1$
		
		Opened clientUser = Opened.newInstance(this);
		run(new String[] { "-c", changelistString }, clientUser, null);                //$NON-NLS-1$

		if (clientUser.getStatus().getSeverity() == IStatus.ERROR)
		{
			throw new PerforceException(clientUser.getStatus());
		}

		return clientUser.getResult();
	}

	/** @see IPerforceServer#opened(boolean) */
	public IOpenedResult[] opened(boolean allClients) 
		throws PerforceException
	{
		String[] arguments = allClients ? new String[] { "-a" } : new String[0];       //$NON-NLS-1$
		
		Opened clientUser = Opened.newInstance(this);
		run(arguments, clientUser, null); 

		if (clientUser.getStatus().getSeverity() == IStatus.ERROR)
		{
			throw new PerforceException(clientUser.getStatus());
		}

		return clientUser.getResult();
	}
	
	/** @see IPerforceServer#reopen(Integer, String[]) */
	public void reopen(Integer changelist, String[] filenames)
		throws PerforceException
	{
		Reopen clientUser = Reopen.newInstance();
		String[] arguments = new String[filenames.length + 2];
		arguments[0] = "-c";                                                           //$NON-NLS-1$
		arguments[1] = changelist.intValue() == -1 ? 
		               "default" : changelist.toString();                              //$NON-NLS-1$
		
		System.arraycopy(filenames, 0, arguments, 2, filenames.length);
		
		run(arguments, clientUser, null);
		
		if (clientUser.getStatus().getSeverity() == IStatus.ERROR)
		{
			throw new PerforceException(clientUser.getStatus());		
		}
	}	

	/** @see IPerforceServer#resolve(String, boolean) */
	public IResolveResult[] resolve(String spec, boolean simulate)
		throws PerforceException
	{
		Resolve clientUser = Resolve.newInstance();
		
		String[] arguments;
		if (simulate)
		{
			arguments = new String[] { "-n", spec };
		}
		else
		{
			arguments = new String[] { spec };
		}
			
		run(arguments, clientUser, null);

		if (clientUser.getStatus().getSeverity() == IStatus.ERROR)
		{
			throw new PerforceException(clientUser.getStatus());
		}
			
		return clientUser.getResult();
	}
	
	/** @see IPerforceServer#resolved(String) */
	public String resolved(String filename) 
		throws PerforceException 
	{
		Resolved clientUser = Resolved.newInstance();
		run(new String[] { filename }, clientUser, null);

		if (clientUser.getStatus().getSeverity() == IStatus.ERROR)
		{
			throw new PerforceException(clientUser.getStatus());
		}
			
		return clientUser.getResult();		
	}

	/** @see IPerforceServer#submit(String) */
	public ISubmitResult[] submit(ChangeSpec changeSpec, IProgressMonitor monitor)
		throws PerforceException
	{
		Submit clientUser = Submit.newInstanceForOutput(changeSpec, monitor);
		run(new String[]{ "-i" }, clientUser, null);                                   //$NON-NLS-1$

		if (clientUser.getStatus().getSeverity() == IStatus.ERROR)
		{
			throw new PerforceException(clientUser.getStatus());
		}
			
		return clientUser.getResult();
	}
	
	/** @see IPerforceServer#revert(String[]) */
	public void revert(String [] fileNames, IProgressMonitor monitor)
		throws PerforceException
	{
		if (fileNames.length > 0)
		{
			simpleCmd(fileNames, "revert");                                            //$NON-NLS-1$
		}
	}

	/** @see IPerforceServer#delete(String[]) */
	public void delete(String [] fileNames)
		throws PerforceException
	{
		simpleCmd(fileNames, "delete");                                                //$NON-NLS-1$
	}

	/** @see IPerforceServer#integrate(String[]) */
	public void integrate(String from, String to)
		throws PerforceException
	{
		simpleCmd(new String[] { from, to }, "integrate");                             //$NON-NLS-1$
	}

	/** @see IPerforceServer#edit(String[]) */
	public void edit(String [] fileNames, final IProgressMonitor monitor)
		throws PerforceException
	{	
		if (fileNames.length > 0)
		{
			final MultiStatus multiStatus = 
				new MultiStatus(PerforceCorePlugin.ID, IStatus.OK, "", null); 
	
			IPerforceCommand command = new AbstractPerforceCommand(multiStatus)
			{
				public void outputInfo(char level, String infoString)	
				{
					super.outputInfo(level, infoString);
				}
	
				/** @see IPerforceCommand#getCommand() */
				public String getCommand() 
				{
					return "edit";                                                     //$NON-NLS-1$
				}
			};
			
			run(fileNames, command, monitor);
			
			if (command.getStatus().getSeverity() == IStatus.ERROR)
			{
				throw new PerforceException(command.getStatus());
			}
		}
	}

	/** @see IPerforceServer#sync(String, String, IProgressMonitor) */
	public IStatus sync(String path, String revisionSpec, final IProgressMonitor monitor)
		throws PerforceException
	{
		return sync(path, revisionSpec, new LinkedList(), monitor);
	}
	
	/** @see IPerforceServer#sync(String, String, List, IProgressMonitor) */
	public IStatus sync(String path, String revisionSpec,  List result, IProgressMonitor monitor)
		throws PerforceException
	{
		Policy.assertNotEmpty(revisionSpec);

		SyncCommand command = SyncCommand.newInstance(monitor);
		run(new String[] { path + revisionSpec }, command, monitor);

		result.addAll(command.getResult());

		return command.getStatus();
	}

	/** @see IPerforceServer#sync(String, boolean, IProgressMonitor) */
	public String[] sync(String syncSpec, boolean simulate, final IProgressMonitor monitor)
		throws PerforceException
	{
		Policy.assertNotEmpty(syncSpec);

		final MultiStatus multiStatus = 
			new MultiStatus(PerforceCorePlugin.ID, IStatus.OK, "", null); 
		
		final List files = new ArrayList();
				
		IPerforceCommand command = new AbstractPerforceCommand(multiStatus)
		{
			public void outputInfo(char level, String infoString)	
			{
				if (level == '0')
				{
					int pos = infoString.indexOf('#');
					String filename = infoString.substring(0, pos);
					
					files.add(filename);
				}
				
				monitor.subTask(infoString);
			}	
			
			/** @see IPerforceCommand#getCommand() */
			public String getCommand() 
			{
				return "sync";
			}		
		};
					
		String[] arguments;
		if (simulate)
		{
			arguments = new String[] { "-n", syncSpec };
		}
		else
		{
			arguments = new String[] { syncSpec };
		}
			
		run(arguments, command, monitor);

		String[] filenames = (String[]) files.toArray(new String[0]);
		try
		{
			IWhereResult[] whereResults = where(filenames);
				
			Policy.assertTrue(whereResults.length == filenames.length);
				
			for (int i = 0, length = whereResults.length; i < length; i++)
			{
				filenames[i] = whereResults[i].getLocalFilename();
			}
		}
		catch (PerforceException e)
		{
			PerforceCorePlugin.log(e.getStatus());
		}
				
		return filenames;		
	}	

	/** @see IPerforceServer#print(String, String)*/
	public InputStream print(String depotFileName, String revision)
		throws PerforceException
	{
		Policy.assertTrue(depotFileName != null && depotFileName.length() > 0);
		Policy.assertTrue(revision != null && revision.length() > 0);

		InputStream result = null;
		
		IWhereResult whereResult = where(depotFileName);
		depotFileName = whereResult.getClientFilename();
	
		String[] arguments = new String[2];
		arguments[0] = "-q";
		arguments[1] = depotFileName + revision;
		
		Print clientUser = Print.newInstance();
		run(arguments, clientUser, null);
		
		if (clientUser.getStatus().getSeverity() == IStatus.ERROR)
		{
			throw new PerforceException(clientUser.getStatus());
		}
		
		return clientUser.getResult();
	}
	
	/** @see IPerforceServer#diff(String[]) */
	public String[] diff(String[] files) 
		throws PerforceException 
	{
		if (files.length == 0)
		{
			return new String[0];
		}
		
		IWhereResult[] whereResults = where(files);
		for (int i = 0, length = files.length; i < length; i++)
		{
			files[i] = whereResults[i].getClientFilename();
		}

		Diff clientUser = Diff.newInstance();	
		String[] arguments = new String[files.length + 1];
		arguments[0] = "-sr";
		System.arraycopy(files, 0, arguments, 1, files.length);
		
		run(arguments, clientUser, null);

		if (clientUser.getStatus().getSeverity() == IStatus.ERROR)
		{
			throw new PerforceException(clientUser.getStatus());
		}
		
		return clientUser.getResult();
	}

	/** @see IPerforceServer#where(String[]) */
	public IWhereResult where(String filename)
		throws PerforceException
	{
		return where(new String[] { filename })[0];
	}

	/** @see IPerforceServer#where(String[]) */
	public IWhereResult[] where(String[] filenames)
		throws PerforceException
	{
		Where clientUser = Where.newInstance();
		run(filenames, clientUser, null);
	
		if (clientUser.getStatus().getSeverity() == IStatus.ERROR)
		{
			throw new PerforceException(clientUser.getStatus());
		}
				
		return clientUser.getResult();
	}

	/** @see IPerforceServer#where(IResource) */
	public IWhereResult where(IResource resource) 
		throws PerforceException 
	{
		return where(resource.getLocation().toOSString());
	}

	/** @see IPerforceServer#determineFolderPosition(String) */
	public FolderRepositoryRelation determineFolderPosition(String folder)
	{
		try
		{
			if (folder.endsWith("/"))
			{
				folder = folder.substring(0, folder.length() - 1);
			}
			
			IPath folderPath = new Path(folder);
			IPath clientRootPath = getClientRoot();
			
			if (!CoreFileSystemLibrary.isCaseSensitive())
			{
				folderPath = new Path(folderPath.toOSString().toLowerCase());
				clientRootPath = new Path(clientRootPath.toOSString().toLowerCase());
			}
			
			if (clientRootPath.segmentCount() > 0)
			{
				if (!clientRootPath.isPrefixOf(folderPath))
				{
					return FolderRepositoryRelation.OUTSIDE_ROOT;
				}
			}

			if (dirs(folder, null).length > 0)
			{
				return FolderRepositoryRelation.IN_REPOSITORY;
			}

			if (getDepotFileName(folder) != null)
			{
				return FolderRepositoryRelation.IN_VIEW;
			}

			return FolderRepositoryRelation.UNDER_ROOT;
		}
		catch (PerforceException pe)
		{
			PerforceCorePlugin.log(pe.getStatus());
			return null;
		}
	}

	/** @see IPerforceServer#getClient() */
	public IClientForm getClientForm()
		throws PerforceException
	{
		Client clientUser = Client.newInstance();
		run(new String[] { "-o" }, clientUser, null);
		
		if (clientUser.getStatus().getSeverity() == IStatus.ERROR)
		{
			PerforceCorePlugin.log(clientUser.getStatus());
			return null;
		}
		
		return clientUser.getResult();
	}

	/** @see IClientApi#moveFile(IResourceTree, IFile, IFile, IProgressMonitor) */
	public boolean moveFile(
		IResourceTree tree, IFile sourceFile, IFile destinationFile, IProgressMonitor monitor)
	{
		try
		{			
			refreshState(new IFile[] { sourceFile, destinationFile }, monitor);			
			
			LocalFile source = (LocalFile) LocalResource.createFromResource(sourceFile);
			LocalFile destination = (LocalFile) LocalResource.createFromResource(destinationFile);

			ResourceSyncInfo syncInfo = source.getSyncInfo();
			if (syncInfo.getAction().equals(ResourceSyncInfo.ACTION_ADD))
			{
				String resolved = resolved(syncInfo.getClientFileName());
				
				// The file has already been integrated		
				if (resolved != null)
				{
					String originalFile = resolved.substring(0, resolved.indexOf('#'));
					originalFile = getLocalFileName(originalFile);
					
					String otherFile = getLocalFileName(destination.getAbsolutePath());
					
					// The new destination was the original source
					if (originalFile.equals(otherFile))
					{
						return moveToOriginalFile(tree, source, destination, monitor);
					}
					else
					{
						return moveResolvedFile(tree, source, destination, resolved, monitor);
					}
				}
				else
				{
					return moveNewlyAddedFile(tree, source, destination, monitor);
				}
			}

			return 	moveUnopenedFile(tree, source, destination, monitor);
		}
		catch (PerforceException e)
		{
			PerforceCorePlugin.log(e.getStatus());
			
			try
			{
				revert((LocalFile) LocalFile.createFromResource(destinationFile));
			
				File file = new File(destinationFile.getLocation().toOSString());
				if (file.exists())
				{
					file.delete();
				}
			}
			catch (PerforceException ee)
			{
				PerforceCorePlugin.log(e.getStatus());
			}
			throw new RuntimeException();			
		}
		finally
		{
			refreshState(new IFile[] { sourceFile, destinationFile }, monitor);
			PerforceCorePlugin.getPlugin().broadcastChangelistChanges();
		}
	}

	/** @see IPerforceServer#getServerAddress() */
	public String getServerAddress()
		throws PerforceException
	{
		if (serverAddress == null)
		{
			updateServerInfo();
		}
			
		return serverAddress;
	}
	
	/** @see IPerforceServer#getServerRoot() */	
	public String getServerRoot()
		throws PerforceException
	{
		if (serverRoot == null)
		{
			updateServerInfo();
		}
		
		return serverRoot;		
	}
	
	/** @see IPerforceServer#getServerDate() */
	public String getServerDate()
		throws PerforceException
	{
		if (serverDate == null)
		{
			updateServerInfo();
		}
		
		return serverDate;
	}
	
	/** @see IPerforceServer#getServerVersion() */					
	public String getServerVersion()
		throws PerforceException
	{
		if (serverVersion == null)
		{
			updateServerInfo();
		}
		
		return serverVersion;
	}
	
	/** @see IPerforceServer#getServerLicense() */	
	public String getServerLicense()
		throws PerforceException
	{
		if (serverLicense == null)
		{
			updateServerInfo();
		}
		
		return serverLicense;
	}

	/** @see IPerforceServer#validateConnection(IProgressMonitor) */
	public void validateConnection(IProgressMonitor monitor)
		throws PerforceException
	{
		try 
		{
			Connection connection = new Connection(this);
			try
			{
				connection.open(monitor);
			}
			finally
			{
				connection.close();
			}			
			monitor.worked(1);
			
						
			IInfoResult result = info(monitor);
			monitor.worked(1);
		
			if (result.getClientName() == null || !result.getClientName().equals(getClient()))
			{
				throw new PerforceException(
					new Status(Status.ERROR, PerforceCorePlugin.ID, 0,
						Policy.bind("PerforceServer.invalidClient"), null));
			}

			if (result.getUserName() == null || !result.getUserName().equals(getUser()))
			{
				throw new PerforceException(
					new Status(Status.ERROR, PerforceCorePlugin.ID, 0,
	                   Policy.bind("RepositoryLocation.invalidUser"), null));
			}
		} 
		catch (PerforceException e) 
		{
			throw e;
		}
	}

	/** @see IPerforceServer#filelog() */
	public ILogEntry[] filelog(RemoteFile remoteFile, int maxRevs, boolean follow)
		throws PerforceException
	{
		String[] arguments;
		if (follow)
		{
			arguments = new String[]
			{ 
				"-i", "-l", "-m", 
				Integer.toString(maxRevs), 
				remoteFile.getDepotFilename()
			};
		}
		else
		{
			arguments = new String[]
			{ 
				"-l", "-m", 
				Integer.toString(maxRevs), 
				remoteFile.getDepotFilename() 
			};
		}
		
		Filelog filelogUser = Filelog.newInstance(this, new NullProgressMonitor());
		run(arguments, filelogUser, null);

		if (filelogUser.getStatus().getSeverity() == IStatus.ERROR)
		{
			throw new PerforceException(filelogUser.getStatus());
		}

		return filelogUser.getResult();
	}
	
	/** @see IPerforceServer#findAllFiles(String, IProgressMonitor) */
	public RemoteFile[] findAllFiles(String filespec, IProgressMonitor monitor)
		throws PerforceException 
	{
		List result = new ArrayList();
		
		Fstat clientUser = Fstat.newInstance(monitor);
		run(new String[] { "-C", "//.../" + filespec }, clientUser, monitor);
		
		ResourceSyncInfo[] syncInfos = clientUser.getResult();
		for (int i = 0; i < syncInfos.length; i++)
		{
			result.add(new RemoteFile(this, syncInfos[i].getDepotFileName(), "#head"));
		}
		
		return (RemoteFile[]) result.toArray(new RemoteFile[result.size()]);
	}
	
	/** @see IPerforceServer#find(String, IProgressMonitor) */
	public RemoteResource find(String name, IProgressMonitor monitor) 
		throws PerforceException 
	{
		RemoteResource[] children = members(monitor);
		for (int i = 0, length = children.length; i < length; i++)
		{
			if (name.startsWith(children[i].getDepotFilename()))
			{
				return children[i].find(name, monitor);
			}
		}		
		
		return null;
	}

	/** @see IPerforceServer#fstat(String, IProgressMonitor) */
	public ResourceSyncInfo[] fstat(String file, IProgressMonitor monitor) 
		throws PerforceException
	{
		Fstat clientUser = Fstat.newInstance(monitor);
		run(new String[] { "-C", file }, clientUser, monitor);                     //$NON-NLS-1$

		if (clientUser.getStatus().getSeverity() == IStatus.ERROR)
		{
			throw new PerforceException(clientUser.getStatus());
		}

		return clientUser.getResult();
	}

	/** @see IPerforceServer#info() */
	public synchronized IInfoResult info(IProgressMonitor monitor)
		throws PerforceException		
	{		
		if (perforceInfoResult == null)
		{
			Info clientUser = Info.newInstance(Policy.monitorFor(monitor));
			run(new String[0], clientUser, monitor);
			
			perforceInfoResult = clientUser.getResult();
		}
		
		return perforceInfoResult;
	}

	/** @see IPerforceServer#add(String[]) */
	public void add(String[] fileNames, IProgressMonitor monitor)
		throws PerforceException
	{
		simpleCmd(fileNames, "add");
	}	
	
	/** Call this method to retrieve the connection object.
	  *
	  * @return the connection object. 
	  */
	public Connection getConnection()
	{
		return connection;
	}	
	
	/** Called to update the password cache.
	  * 
	  * @throws PerforceException in case of an unexpected error.
	  */
	public void updateCache()
		throws PerforceException
	{
		updateCache(user, password, true);
		password = null;
	}
	
	// ----------------------------------------------------------------------------- Private Methods
	
	/** Retrieves the depot pathname of the specified path. 
	  * 
	  * @param path the input path.
	  * @return the path in depot syntax.
	  * 
	  * @throws PerforceException in case of an error.
	  */
	private String getDepotFileName(String path) 
		throws PerforceException
	{
		IWhereResult whereResult = where(path);
		return whereResult.getDepotFilename();
	}
	
	/** Retrieves the local filename of the specified path. 
	  * 
	  * @param path the input path.
	  * @return the path in depot syntax.
	  * 
	  * @throws PerforceException in case of an error.
	  */
	private String getLocalFileName(String path)
		throws PerforceException
	{
		IWhereResult whereResult = where(path);
		return whereResult.getLocalFilename();		
	}

	/** Returns the path of the given file in form of an array.
	  * 
	  * @param file the file
	  * @return arrray[1] containg the path.
	  */
	private String[] fileNames(LocalFile file)
	{
		return new String[] { file.getAbsolutePath() };
	}	

	/** @see IClientApi#revert(String[]) */
	private void revert(LocalFile[] files)
		throws PerforceException
	{
		revert(LocalFile.fileNames(files), null);
	}

	/** @see IClientApi#revert(String[]) */
	private void revert(LocalFile file)
		throws PerforceException
	{
		revert(new LocalFile[] { file });
	}

	/** @see IClientApi#add(String[]) */
	private void add(LocalFile[] files)
		throws PerforceException
	{
		add(LocalFile.fileNames(files), null);
	}

	/** @see IClientApi#add(String[]) */
	private void add(LocalFile file)
		throws PerforceException
	{
		add(new LocalFile[] { file });
	}

	/** @see IClientApi#edit(String[]) */
	private void edit(LocalFile[] files)
		throws PerforceException
	{
		edit(LocalFile.fileNames(files), new NullProgressMonitor());
	}

	/** @see IClientApi#edit(String[]) */
	private void edit(LocalFile file)
		throws PerforceException
	{
		edit(new LocalFile[] { file });
	}

	/** @see IClientApi#delete(String[]) */
	private void delete(LocalFile file)
		throws PerforceException
	{
		delete(fileNames(file));
	}
	
	/** @see P4TeamProvider#refreshState(
	  *       IResource[], int, IProgressMonitor) 
	  */
	private void refreshState(IFile[] files, IProgressMonitor monitor)
	{
		P4TeamProvider provider =
			(P4TeamProvider) RepositoryProvider.getProvider(
				files[0].getProject());
	
		try
		{		
			provider.refreshState(files, IResource.DEPTH_ZERO, monitor);
		}
		catch (TeamException e)
		{
			PerforceCorePlugin.log(e.getStatus());
		}
	}	

	/** Performs Perforce move for files that are not currently opened for .. 
	  * 
	  * @param tree the resource tree.
	  * @param source the source of the operation.
	  * @param destination the destination.
	  * @param monitor for progress feedback.
	  * @return true if we handled the move.
	  * 
	  * @throws PerforceException in case of an error.
	  */
	private boolean moveUnopenedFile(
		IResourceTree tree, LocalFile source, LocalFile destination, IProgressMonitor monitor)
		throws PerforceException 
	{
		integrate(
			source.getAbsolutePath(), destination.getAbsolutePath());
			
		if (source.isCheckedOut())
		{
			edit(destination);
			
			tree.standardMoveFile((IFile) source.toResource(), (IFile) destination.toResource(),
				IResource.FORCE | IResource.KEEP_HISTORY, monitor);
		
			revert(source);
			delete(source);
		
			return true;				
		}
		return false;
	}

	/** Performs perforce move for a file that has been newly added to Perforce,
	  * but not via integrate.
	  * 
	  * @param tree the resource tree.
	  * @param source the source of the operation.
	  * @param destination the destination.
	  * @param monitor for progress feedback.
	  * @return true if we handled the move.
	  * 
	  * @throws PerforceException in case of an error.
	  */  
	private boolean moveNewlyAddedFile(
		IResourceTree tree, LocalFile source, LocalFile destination, IProgressMonitor monitor)		
		throws PerforceException 
	{
		tree.standardMoveFile((IFile) source.toResource(), (IFile) destination.toResource(),
			IResource.FORCE | IResource.KEEP_HISTORY, monitor);

		add(destination);
		revert(source);		
		
		return true;			
	}

	/** Performs a move in which the destination is the file from which the
	  * original rename/integrate cycle started.
	  * 
	  * @param tree the resource tree.
	  * @param source the source of the operation.
	  * @param destination the destination.
	  * @param monitor for progress feedback.
	  * @return true if we handled the move.
	  * 
	  * @throws PerforceException in case of an error
	  */
	private boolean moveToOriginalFile(
		IResourceTree tree, LocalFile source, LocalFile destination, IProgressMonitor monitor)
		throws PerforceException 
	{
		revert(destination);
		edit(destination);
		
		tree.standardMoveFile((IFile) source.toResource(), (IFile) destination.toResource(),
			IResource.FORCE | IResource.KEEP_HISTORY, monitor);

		revert(source);
				 				
		return true;
	}

	/** Performs a Perforce move for a file that has already been integrated. 
	  * 
	  * @param tree the resource tree.
	  * @param source the source of the operation.
	  * @param destination the destination.
	  * @param monitor for progress feedback.
	  * @param resolved the original file path.
	  * @return true if we handled the move.
	  * 
	  * @throws PerforceException in case of an error.
	  */
	private boolean moveResolvedFile(IResourceTree tree, LocalFile source, LocalFile destination, 
		String resolved, IProgressMonitor monitor)
		throws PerforceException 
	{
		integrate(resolved, destination.getAbsolutePath());
		edit(destination);

		tree.standardMoveFile((IFile) source.toResource(), (IFile) destination.toResource(),
			IResource.FORCE | IResource.KEEP_HISTORY, monitor);

		revert(source);
		
		return true;
	}

	/** Performs a simple file base Perforce operation.
	  * 
	  * @param fileNames array of file names.
	  * @param cmd the command.
	  * 
	  * @throws PerforceException in case of an error.
	  */
	private void simpleCmd(String[] fileNames, String cmd) 
		throws PerforceException
	{
		SimpleCmd clientUser = SimpleCmd.newInstance(cmd); 
		run(fileNames, clientUser, null); 
		
		if (clientUser.getStatus().getSeverity() == IStatus.ERROR)
		{
			throw new PerforceException(clientUser.getStatus());		
		}
	}

	/** Returns the current client's perforce root.
	  * 
	  * @return the client root.
	  * 
	  * @throws PerforceException in case of an unexpected error.
	  */
	private IPath getClientRoot()
		throws PerforceException
	{
		if (clientRoot == null)
		{
			updateClientInfo();
		}
			
		return clientRoot;
	}

	/** Called to update the internal info about the server. 
	  * 
	  * @throws PerforceException in case of an error-
	  */
	private void updateServerInfo()
		throws PerforceException
	{
		IInfoResult result = info(null);
		
		serverAddress = result.getServerAddress();
		serverRoot = result.getServerRoot();
		serverDate = result.getServerDate();
		serverVersion = result.getServerVersion();
		serverLicense = result.getServerLicense();
	}

	/** Called to update the internal info about the client.  
	  * 
	  * @throws PerforceException in case of an unexpected error.
	  */
	private void updateClientInfo()
		throws PerforceException
	{		
		IInfoResult result = info(null);
		if (result.getClientRoot() == null)
		{
			clientRoot = null;
		}
		else
		if (result.getClientRoot().equalsIgnoreCase("null"))
		{
			clientRoot = Path.EMPTY;
		}
		else
		{
			clientRoot = new Path(result.getClientRoot());
		}
	}
	
	/** Runs the given command with the specified arguments.
	  *
	  * @param arguments the arguments of the operation.
	  * @param command the command to execute.
	  * @param monitor monitor for progress feedback.
	  * 
	  * @throws PerforceException in case of an unexpected error.  
	  */
	private void run(String[] arguments, IPerforceCommand command, IProgressMonitor monitor)
		throws PerforceException
	{		
		monitor = Policy.monitorFor(monitor);
		try
		{
			synchronized (connection)
			{
				connection.open(monitor);			
				connection.run(arguments, command, monitor);
			}
		}
		finally
		{
			monitor.done();
		}
	}

	/** Cache the user info in the keyring. 
	  * 
	  * @param user the username.
	  * @param password the password.
	  * @param createIfAbsent indicates whether a new entry should be created when needed.
	  * 
	  * @throws PerforceException in case of an error. 
	  */
	private void updateCache(String user, String password, boolean createIfAbsent) 
		throws PerforceException
	{
		Map map = Platform.getAuthorizationInfo(FAKE_URL, getUniqueName(), AUTH_SCHEME);
		if (map == null)
		{
			if (!createIfAbsent)
			{
				return;
			}
			
			map = new HashMap(1);
		}
		
		if (password != null)
		{
			map.put(INFO_PASSWORD, password);
		}
		
		try
		{
			Platform.addAuthorizationInfo(FAKE_URL, getUniqueName(), AUTH_SCHEME, map);
		}
		catch (CoreException e)
		{
			PerforceCorePlugin.log(e.getStatus());
			throw new PerforceException(
				new Status(Status.ERROR, PerforceCorePlugin.ID, Status.ERROR, "Error", e));
		}
	}
}
